import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutterproject/widget/spinning_wheel/utils.dart';

class SpinningWheel extends StatefulWidget {
  final double width;

  final double height;

  final Image image;

  final int dividers;

  final double initialSpinAngle;

  final double spinResistance;

  final bool canInteractWhileSpinning;

  final Image? secondaryImage;

  final double? secondaryImageHeight;

  final double? secondaryImageWidth;

  final double? secondaryImageTop;

  final double? secondaryImageLeft;

  final Function? onUpdate;

  final Function? onEnd;

  final Stream? shouldStartOrStop;
  final Key? key;

  SpinningWheel(
    this.image, {
    required this.width,
    required this.height,
    required this.dividers,
    this.initialSpinAngle = 0.0,
    this.spinResistance = 0.5,
    this.canInteractWhileSpinning = true,
    this.secondaryImage,
    this.secondaryImageHeight,
    this.secondaryImageWidth,
    this.secondaryImageTop,
    this.secondaryImageLeft,
    this.onUpdate,
    this.onEnd,
    this.shouldStartOrStop,
    this.key,
  }) : super(key: key);

  @override
  SpinningWheelState createState() => SpinningWheelState();
}

class SpinningWheelState extends State<SpinningWheel>
    with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<double> _animation;

  late SpinVelocity _spinVelocity;
  late NonUniformCircularMotion _motion;

  Offset? _localPositionOnPanUpdate;

  double _totalDuration = 0;

  double _initialCircularVelocity = 0;

  double? _dividerAngle;

  double _currentDistance = 0;

  late double _initialSpinAngle;

  int? _currentDivider;

  late bool _isBackwards;

  DateTime? _offsetOutsideTimestamp;

  RenderBox? _renderBox;

  StreamSubscription? _subscription;

  double myAngle = 0;
  int myAngle2 = 0;

  @override
  void initState() {
    super.initState();

    _spinVelocity = SpinVelocity(width: widget.width, height: widget.height);
    _motion = NonUniformCircularMotion(resistance: widget.spinResistance);

    _animationController = AnimationController(
      vsync: this,
      duration: Duration(),
    );
    _animation = Tween(begin: 0.0, end: 1.0).animate(
        CurvedAnimation(parent: _animationController, curve: Curves.easeOut));

    _dividerAngle = _motion.anglePerDivision(widget.dividers);
    _initialSpinAngle = widget.initialSpinAngle;

    _animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        // _stopAnimation();
        widget.onEnd?.call();
      }
    });
    _animation.addListener(() {
      myAngle = pi * 2 * 7 * _animation.value + myAngle2 * pi * 2 / 4 - pi / 4;
      setState(() {});
    });

    if (widget.shouldStartOrStop != null) {
      _subscription = widget.shouldStartOrStop!.listen(_startOrStop);
    }
  }

  _startOrStop(dynamic velocity) {
    if (_animationController.isAnimating) {
      _stopAnimation();
    } else {
      final pixelsPerSecondY = velocity ?? 8000.0;
      _localPositionOnPanUpdate = Offset(250.0, 250.0);
      _startAnimation(Offset(0.0, pixelsPerSecondY));
    }
  }

  double get topSecondaryImage =>
      widget.secondaryImageTop ??
      (widget.height / 2) - (widget.secondaryImageHeight! / 2);

  double get leftSecondaryImage =>
      widget.secondaryImageLeft ??
      (widget.width / 2) - (widget.secondaryImageWidth! / 2);

  double get widthSecondaryImage => widget.secondaryImageWidth ?? widget.width;

  double get heightSecondaryImage =>
      widget.secondaryImageHeight ?? widget.height;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: widget.height,
      width: widget.width,
      child: Stack(
        children: [
          Transform.rotate(
              angle: myAngle, child: Container(child: widget.image)),
          if (widget.secondaryImage != null)
            Positioned(
                top: topSecondaryImage,
                left: leftSecondaryImage,
                child: SizedBox(
                  height: heightSecondaryImage,
                  width: widthSecondaryImage,
                  child: widget.secondaryImage,
                ))
          else
            Container(),
        ],
      ),
    );
  }

  bool get _userCanInteract =>
      !_animationController.isAnimating || widget.canInteractWhileSpinning;

  void _updateLocalPosition(Offset position) {
    _renderBox ??= context.findRenderObject() as RenderBox?;
    _localPositionOnPanUpdate = _renderBox!.globalToLocal(position);
  }

  bool _contains(Offset p) => Size(widget.width, widget.height).contains(p);

  void _updateAnimationValues() {
    if (_animationController.isAnimating) {
      final currentTime = _totalDuration * _animation.value;
      _currentDistance =
          _motion.distance(_initialCircularVelocity, currentTime);
      if (_isBackwards) {
        _currentDistance = -_currentDistance;
      }
    }
    final modulo = _motion.modulo(_currentDistance + _initialSpinAngle);
    _currentDivider = widget.dividers - (modulo ~/ _dividerAngle) as int?;
    if (_animationController.isCompleted) {
      _initialSpinAngle = modulo;
      _currentDistance = 0;
    }
  }

  void _moveWheel(DragUpdateDetails details) {
    if (!_userCanInteract) return;

    if (_offsetOutsideTimestamp != null) return;

    _updateLocalPosition(details.globalPosition);

    if (_contains(_localPositionOnPanUpdate!)) {
      final angle = _spinVelocity.offsetToRadians(_localPositionOnPanUpdate!);
      setState(() {
        _currentDistance = angle - _initialSpinAngle;
      });
    } else {
      _offsetOutsideTimestamp = DateTime.now();
    }
  }

  void _stopAnimation() {
    if (!_userCanInteract) return;

    _offsetOutsideTimestamp = null;
    _animationController.stop();
    _animationController.reset();

    widget.onEnd?.call(_currentDivider);
  }

  void _startAnimationOnPanEnd(DragEndDetails details) {
    if (!_userCanInteract) return;

    if (_offsetOutsideTimestamp != null) {
      final difference = DateTime.now().difference(_offsetOutsideTimestamp!);
      _offsetOutsideTimestamp = null;
      if (difference.inMilliseconds > 50) return;
    }

    if (_localPositionOnPanUpdate == null) return;

    _startAnimation(details.velocity.pixelsPerSecond);
  }

  void _startAnimation(Offset pixelsPerSecond) {
    final velocity =
        _spinVelocity.getVelocity(_localPositionOnPanUpdate!, pixelsPerSecond);

    _localPositionOnPanUpdate = null;
    _isBackwards = velocity < 0;
    _initialCircularVelocity = pixelsPerSecondToRadians(velocity.abs());
    _totalDuration = _motion.duration(_initialCircularVelocity);

    _animationController.duration = Duration(milliseconds: (1000).round());

    _animationController.reset();
    _animationController.forward();
  }

  void myStartAnimation(int index) {
    myAngle2 = index;
    _animationController.duration = Duration(milliseconds: (3000).round());
    _animationController.reset();
    _animationController.forward();
  }

  @override
  void dispose() {
    _animationController.dispose();
    if (_subscription != null) {
      _subscription!.cancel();
    }
    super.dispose();
  }
}
